/*
 * Copyright (c) 2006-2023, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include <rtthread.h>
#include <rtdevice.h>
#include "board.h"
#include "drv_ospi.h"
#include "drivers/dev_spi.h"
#include "drv_config.h"

#ifdef RT_USING_OSPI

#define DRV_DEBUG
#define LOG_TAG              "drv.ospi"
#include <drv_log.h>

#ifndef RT_MIN
#define RT_MIN(a, b) ((a) < (b) ? (a) : (b))
#endif

#if defined(BSP_USING_OSPI)

struct stm32_ospi_bus
{
    OSPI_HandleTypeDef OSPI_Handler;
    char *bus_name;
#ifdef BSP_OSPI_USING_DMA
    MDMA_HandleTypeDef hmdma_octospi1_fifo_th;
    struct rt_completion cpt;  // 添加完成量用于DMA传输同步
#endif
};

struct rt_spi_bus _ospi_bus1;
struct stm32_ospi_bus _stm32_ospi_bus;

static void ospi_send_cmd(struct stm32_ospi_bus *ospi_bus, struct rt_qspi_message *message)
{
    RT_ASSERT(ospi_bus != RT_NULL);
    RT_ASSERT(message != RT_NULL);

    OSPI_RegularCmdTypeDef Cmdhandler = {0};

    /* set OSPI cmd struct */
    Cmdhandler.OperationType = HAL_OSPI_OPTYPE_COMMON_CFG;
    Cmdhandler.FlashId = HAL_OSPI_FLASH_ID_1;
    Cmdhandler.Instruction = message->instruction.content;
    Cmdhandler.Address = message->address.content;
    Cmdhandler.DummyCycles = message->dummy_cycles;
    
    /* Instruction mode */
    if (message->instruction.qspi_lines == 0)
        Cmdhandler.InstructionMode = HAL_OSPI_INSTRUCTION_NONE;
    else if (message->instruction.qspi_lines == 1)
        Cmdhandler.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE;
    else if (message->instruction.qspi_lines == 2)
        Cmdhandler.InstructionMode = HAL_OSPI_INSTRUCTION_2_LINES;
    else if (message->instruction.qspi_lines == 4)
        Cmdhandler.InstructionMode = HAL_OSPI_INSTRUCTION_4_LINES;

    /* Address mode */
    if (message->address.qspi_lines == 0)
        Cmdhandler.AddressMode = HAL_OSPI_ADDRESS_NONE;
    else if (message->address.qspi_lines == 1)
        Cmdhandler.AddressMode = HAL_OSPI_ADDRESS_1_LINE;
    else if (message->address.qspi_lines == 2)
        Cmdhandler.AddressMode = HAL_OSPI_ADDRESS_2_LINES;
    else if (message->address.qspi_lines == 4)
        Cmdhandler.AddressMode = HAL_OSPI_ADDRESS_4_LINES;

    /* Address size */
    if (message->address.size == 24)
        Cmdhandler.AddressSize = HAL_OSPI_ADDRESS_24_BITS;
    else
        Cmdhandler.AddressSize = HAL_OSPI_ADDRESS_32_BITS;

    /* Data mode */
    if (message->qspi_data_lines == 0)
        Cmdhandler.DataMode = HAL_OSPI_DATA_NONE;
    else if (message->qspi_data_lines == 1)
        Cmdhandler.DataMode = HAL_OSPI_DATA_1_LINE;
    else if (message->qspi_data_lines == 2)
        Cmdhandler.DataMode = HAL_OSPI_DATA_2_LINES;
    else if (message->qspi_data_lines == 4)
        Cmdhandler.DataMode = HAL_OSPI_DATA_4_LINES;

    Cmdhandler.SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD;
    Cmdhandler.NbData = message->parent.length;
    
    HAL_OSPI_Command(&ospi_bus->OSPI_Handler, &Cmdhandler, HAL_OSPI_TIMEOUT_DEFAULT_VALUE);
}

static rt_ssize_t ospixfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
    rt_ssize_t result = 0;
    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(device->bus != RT_NULL);

    struct rt_qspi_message *ospi_message = (struct rt_qspi_message *)message;
    struct stm32_ospi_bus *ospi_bus = device->bus->parent.user_data;

    const rt_uint8_t *sndb = message->send_buf;
    rt_uint8_t *rcvb = message->recv_buf;
    rt_size_t remaining = message->length;
    rt_uint8_t *ptr = rcvb ? rcvb : (rt_uint8_t *)sndb;

#ifdef BSP_OSPI_USING_SOFTCS
    if (message->cs_take && (device->cs_pin != PIN_NONE))
    {
        rt_pin_write(device->cs_pin, PIN_LOW);
    }
#endif

    while (remaining > 0)
    {
        rt_size_t chunk = RT_MIN(remaining, 65535);
        
        /* 处理数据对齐 */
        rt_uint32_t* dma_aligned_buffer = RT_NULL;
        rt_uint32_t* p_txrx_buffer = RT_NULL;
        
        if ((sndb || rcvb) && (chunk >= 10)) // DMA传输最小长度阈值
        {
#if defined(SOC_SERIES_STM32H7)
            if (!RT_IS_ALIGN((rt_uint32_t)ptr, 32))
            {
                dma_aligned_buffer = (rt_uint32_t *)rt_malloc_align(chunk, 32);
                if (sndb) rt_memcpy(dma_aligned_buffer, ptr, chunk);
                p_txrx_buffer = dma_aligned_buffer;
                rt_hw_cpu_dcache_ops(RT_HW_CACHE_FLUSH, dma_aligned_buffer, chunk);
            }
            else
            {
                p_txrx_buffer = (rt_uint32_t *)ptr;
            }
#endif
        }

        /* 发送数据 */
        if (sndb)
        {
            ospi_send_cmd(ospi_bus, ospi_message);
            if (ospi_message->parent.length != 0)
            {
#ifdef BSP_OSPI_USING_DMA
                if (HAL_OSPI_Transmit_DMA(&ospi_bus->OSPI_Handler, (rt_uint8_t *)p_txrx_buffer) == HAL_OK)
                {
                    if (rt_completion_wait(&ospi_bus->cpt, RT_WAITING_FOREVER) != RT_EOK)
                    {
                        result = -RT_ETIMEOUT;
                        break;
                    }
                }
#else
                if (HAL_OSPI_Transmit(&ospi_bus->OSPI_Handler, (rt_uint8_t *)p_txrx_buffer, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
                {
                    result = -RT_ERROR;
                    break;
                }
#endif
            }
        }
        /* 接收数据 */
        else if (rcvb)
        {
            ospi_send_cmd(ospi_bus, ospi_message);
#ifdef BSP_OSPI_USING_DMA
            if (HAL_OSPI_Receive_DMA(&ospi_bus->OSPI_Handler, (rt_uint8_t *)p_txrx_buffer) == HAL_OK)
            {
                if (rt_completion_wait(&ospi_bus->cpt, RT_WAITING_FOREVER) != RT_EOK)
                {
                    result = -RT_ETIMEOUT;
                    break;
                }
            }
#else
            if (HAL_OSPI_Receive(&ospi_bus->OSPI_Handler, (rt_uint8_t *)p_txrx_buffer, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
            {
                result = -RT_ERROR;
                break;
            }
#endif
        }

        /* 处理非对齐缓冲区的数据回写 */
        if (dma_aligned_buffer && rcvb)
        {
#if defined(SOC_SERIES_STM32H7)
            rt_hw_cpu_dcache_ops(RT_HW_CACHE_INVALIDATE, dma_aligned_buffer, chunk);
#endif
            rt_memcpy(ptr, dma_aligned_buffer, chunk);
        }

        if (dma_aligned_buffer)
        {
#if defined(SOC_SERIES_STM32H7)
            rt_free_align(dma_aligned_buffer);
#endif
        }

        ptr += chunk;
        remaining -= chunk;
        result += chunk;
    }

#ifdef BSP_OSPI_USING_SOFTCS
    if (message->cs_release && (device->cs_pin != PIN_NONE))
    {
        rt_pin_write(device->cs_pin, PIN_HIGH);
    }
#endif

    return result;
}

static rt_err_t ospi_configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration)
{
    RT_ASSERT(device != RT_NULL);
    
    struct stm32_ospi_bus *ospi_bus = device->bus->parent.user_data;
    
    /* 初始化OSPI硬件 */
    ospi_bus->OSPI_Handler.Instance = OCTOSPI1;
    ospi_bus->OSPI_Handler.Init.FifoThreshold = 4;
    ospi_bus->OSPI_Handler.Init.DualQuad = HAL_OSPI_DUALQUAD_DISABLE;
    ospi_bus->OSPI_Handler.Init.MemoryType = HAL_OSPI_MEMTYPE_MICRON;
    ospi_bus->OSPI_Handler.Init.DeviceSize = 24;
    ospi_bus->OSPI_Handler.Init.ChipSelectHighTime = 1;
    ospi_bus->OSPI_Handler.Init.FreeRunningClock = HAL_OSPI_FREERUNCLK_DISABLE;
    ospi_bus->OSPI_Handler.Init.ClockMode = HAL_OSPI_CLOCK_MODE_0;
    ospi_bus->OSPI_Handler.Init.WrapSize = HAL_OSPI_WRAP_NOT_SUPPORTED;
    ospi_bus->OSPI_Handler.Init.ClockPrescaler = 32;
    ospi_bus->OSPI_Handler.Init.SampleShifting = HAL_OSPI_SAMPLE_SHIFTING_HALFCYCLE;
    ospi_bus->OSPI_Handler.Init.DelayHoldQuarterCycle = HAL_OSPI_DHQC_DISABLE;
    ospi_bus->OSPI_Handler.Init.ChipSelectBoundary = 0;
    ospi_bus->OSPI_Handler.Init.DelayBlockBypass = HAL_OSPI_DELAY_BLOCK_BYPASSED;
    
    if (HAL_OSPI_Init(&ospi_bus->OSPI_Handler) != HAL_OK)
    {
        LOG_E("OSPI init failed!");
        return -RT_ERROR;
    }
    
    /* 配置OSPI管理器 */
    OSPIM_CfgTypeDef sOspiManagerCfg = {0};
    sOspiManagerCfg.ClkPort = 1;
    sOspiManagerCfg.NCSPort = 1;
    sOspiManagerCfg.IOLowPort = HAL_OSPIM_IOPORT_1_LOW;
    
    if (HAL_OSPIM_Config(&ospi_bus->OSPI_Handler, &sOspiManagerCfg, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        LOG_E("OSPIM config failed!");
        return -RT_ERROR;
    }
    
    #ifdef BSP_OSPI_USING_DMA
    /* DMA控制器时钟使能 */
    __HAL_RCC_MDMA_CLK_ENABLE();
    
    /* 配置DMA */
     ospi_bus->hmdma_octospi1_fifo_th.Instance = MDMA_Channel0;
     ospi_bus->hmdma_octospi1_fifo_th.Init.Request = MDMA_REQUEST_OCTOSPI1_FIFO_TH;
     ospi_bus->hmdma_octospi1_fifo_th.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER;
     ospi_bus->hmdma_octospi1_fifo_th.Init.Priority = MDMA_PRIORITY_LOW;
     ospi_bus->hmdma_octospi1_fifo_th.Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE;
     ospi_bus->hmdma_octospi1_fifo_th.Init.SourceInc = MDMA_SRC_INC_BYTE;
     ospi_bus->hmdma_octospi1_fifo_th.Init.DestinationInc = MDMA_DEST_INC_DISABLE;
     ospi_bus->hmdma_octospi1_fifo_th.Init.SourceDataSize = MDMA_SRC_DATASIZE_BYTE;
     ospi_bus->hmdma_octospi1_fifo_th.Init.DestDataSize = MDMA_DEST_DATASIZE_BYTE;
     ospi_bus->hmdma_octospi1_fifo_th.Init.DataAlignment = MDMA_DATAALIGN_PACKENABLE;
     ospi_bus->hmdma_octospi1_fifo_th.Init.BufferTransferLength = 128;
     ospi_bus->hmdma_octospi1_fifo_th.Init.SourceBurst = MDMA_SOURCE_BURST_SINGLE;
     ospi_bus->hmdma_octospi1_fifo_th.Init.DestBurst = MDMA_DEST_BURST_SINGLE;
     ospi_bus->hmdma_octospi1_fifo_th.Init.SourceBlockAddressOffset = 0;
     ospi_bus->hmdma_octospi1_fifo_th.Init.DestBlockAddressOffset = 0;
    
    if (HAL_MDMA_Init(&ospi_bus->hmdma_octospi1_fifo_th) != HAL_OK)
    {
        LOG_E("DMA init failed!");
        return -RT_ERROR;
    }

    if (HAL_MDMA_ConfigPostRequestMask(&ospi_bus->hmdma_octospi1_fifo_th, 0, 0) != HAL_OK)
    {
        LOG_E("DMA init failed!");
        return -RT_ERROR;
    }
    
    /* 链接DMA到OSPI */
    __HAL_LINKDMA(&ospi_bus->OSPI_Handler, hmdma, ospi_bus->hmdma_octospi1_fifo_th);
    
    /* 配置DMA中断 */
    HAL_NVIC_SetPriority(MDMA_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(MDMA_IRQn);
    #endif
    
    return RT_EOK;
}

static const struct rt_spi_ops stm32_ospi_ops =
{
    .configure = ospi_configure,
    .xfer = ospixfer,
};

static int stm32_ospi_register_bus(struct stm32_ospi_bus *ospi_bus, const char *name)
{
    RT_ASSERT(ospi_bus != RT_NULL);
    RT_ASSERT(name != RT_NULL);

    _ospi_bus1.parent.user_data = ospi_bus;
    return rt_qspi_bus_register(&_ospi_bus1, name, &stm32_ospi_ops);
}

rt_err_t rt_hw_ospi_device_attach(const char *bus_name, const char *device_name, rt_base_t cs_pin, rt_uint8_t data_line_width, void (*enter_qspi_mode)(), void (*exit_qspi_mode)())
{
    struct rt_qspi_device *ospi_device = RT_NULL;
    rt_err_t result = RT_EOK;

    RT_ASSERT(bus_name != RT_NULL);
    RT_ASSERT(device_name != RT_NULL);
    RT_ASSERT(data_line_width == 1 || data_line_width == 2 || data_line_width == 4);

    ospi_device = (struct rt_qspi_device *)rt_malloc(sizeof(struct rt_qspi_device));
    if (ospi_device == RT_NULL)
    {
        LOG_E("no memory, ospi bus attach device failed!");
        return -RT_ENOMEM;
    }

    ospi_device->config.qspi_dl_width = data_line_width;
    ospi_device->enter_qspi_mode = enter_qspi_mode;
    ospi_device->exit_qspi_mode = exit_qspi_mode;

#ifdef BSP_OSPI_USING_SOFTCS
    result = rt_spi_bus_attach_device_cspin(&ospi_device->parent, device_name, bus_name, cs_pin, RT_NULL);
#else
    result = rt_spi_bus_attach_device_cspin(&ospi_device->parent, device_name, bus_name, PIN_NONE, RT_NULL);
#endif /* BSP_OSPI_USING_SOFTCS */

    if (result != RT_EOK && ospi_device)
    {
        rt_free(ospi_device);
    }

    return result;
}

static int rt_hw_ospi_bus_init(void)
{
#ifdef BSP_OSPI_USING_DMA
    rt_completion_init(&_stm32_ospi_bus.cpt);
#endif
    return stm32_ospi_register_bus(&_stm32_ospi_bus, "ospi1");
}
INIT_BOARD_EXPORT(rt_hw_ospi_bus_init);

#endif /* BSP_USING_OSPI */
#endif /* RT_USING_OSPI */

#ifdef BSP_OSPI_USING_DMA
void MDMA_IRQHandler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();

    HAL_MDMA_IRQHandler(&_stm32_ospi_bus.hmdma_octospi1_fifo_th);

    /* leave interrupt */
    rt_interrupt_leave();
}
#endif /* BSP_OSPI_USING_DMA */

#ifdef BSP_OSPI_USING_DMA
void HAL_OSPI_TxCpltCallback(OSPI_HandleTypeDef *hospi)
{
    struct stm32_ospi_bus *ospi_bus = rt_container_of(hospi, struct stm32_ospi_bus, OSPI_Handler);
    rt_completion_done(&ospi_bus->cpt);
}

void HAL_OSPI_RxCpltCallback(OSPI_HandleTypeDef *hospi)
{
    struct stm32_ospi_bus *ospi_bus = rt_container_of(hospi, struct stm32_ospi_bus, OSPI_Handler);
    rt_completion_done(&ospi_bus->cpt);
}
#endif